package com.wuliang.generate.common.core.utils;

import com.wuliang.generate.common.core.adapter.jpa.EntityFieldTypeAdapter;
import com.wuliang.generate.common.core.adapter.jpa.impl.CollectionFieldTypeAdapter;
import com.wuliang.generate.common.core.adapter.jpa.impl.NumberFieldTypeAdapter;
import com.wuliang.generate.common.core.adapter.jpa.impl.StringFieldTypeAdapter;
import com.wuliang.generate.common.core.basic.BasicEntity;
import com.wuliang.generate.common.core.response.Result;
import com.wuliang.generate.common.core.response.ResultUtils;
import com.wuliang.generate.common.core.response.ReturnCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.jpa.domain.Specification;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.From;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Predicate;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
 * JPA工具类
 *
 * @author 邬魉
 */
@Lazy
@Slf4j
public class PredicatesUtils {

    /**
     * 格式化传入参数，将其作为JPA框架的原生语法糖
     * <p>
     * Predicate: 一个简单或复杂的谓词类型，其实就相当于条件或者是条件组合。 CriteriaBuilder：用来构建CritiaQuery的构建器对象 Root: 实体类对象
     * <p>
     * 传参样例 {@see FightOrderRepositoryTest#testFindAll()}
     *
     * @param conditionalMap 传入参数
     * @return JPA框架原生语法糖
     */
    public static <T extends BasicEntity> Specification<T> getFields(final Map<String, Object> conditionalMap) {
        return getFields(conditionalMap, null);
    }

    /**
     * 格式化传入参数，将其作为JPA框架的原生语法糖
     *
     * @param searchKeywords     要查询的关键字和关键字的值
     * @param searchFuzzyKeyword 模糊关键字字段以及其值
     * @param <T>                规定必须为实体类
     * @return 条件集合
     */
    public static <T extends BasicEntity> Specification<T> getFields(final Map<String, Object> searchKeywords,
                                                                     Map<String, Object> searchFuzzyKeyword) {
//        lombok 表达式
        return (Specification<T>) (root, query, criteriaBuilder) -> {
//            声明一个谓词集合，以待后续使用
            List<Predicate> predicates = new ArrayList<>();
//            遍历前端传过来的关键字集合
            if (searchKeywords != null) {
                //调用方法，以获取谓词集合
                predicates.addAll(createPredicates(searchKeywords, searchFuzzyKeyword, criteriaBuilder, root));
            }
//            最后交由  SQLSession工厂 来根据谓词集合创建查询SQL语句
            return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
        };
    }

    /**
     * 创建条件集合
     *
     * @param conditionalMap     要查询的关键字和关键字的值
     * @param searchFuzzyKeyword 模糊关键字字段以及其值
     * @param criteriaBuilder    条件生成器 {@see CriteriaBuilder}
     * @param from               要查哪个实体类
     * @param <T>                规定必须为实体类
     * @return 条件集合
     */
    private static <T extends BasicEntity> List<Predicate> createPredicates(Map<String, Object> conditionalMap,
                                                                            Map<String, Object> searchFuzzyKeyword,
                                                                            CriteriaBuilder criteriaBuilder,
                                                                            From from) {
        // 声明一个and谓词集合
        List<Predicate> predicates = new ArrayList<>();
        // 声明一个or谓词集合
        List<Predicate> likePredicates = new ArrayList<>();
        // 获取实体类对象的Class
        Class<T> entity = from.getModel().getBindableJavaType();
        // 获取对象内字段名称列表
        List<String> fields = checkFieldName(entity);
        conditionalMap.forEach((key, value) -> {
            if (!fields.contains(key)) {
                return;
            }
            if (value == null) {
                return;
            }
            // 如果当前遍历的为对象，则递归按照上面的步骤来查询
            if (chooseType(value) == EntityFieldTypeEnum.MAP) {
                predicates.addAll(createPredicates(
                        ((Map<String, Object>) value),
                        null,
                        criteriaBuilder,
                        from.join(key, JoinType.LEFT)
                ));
            } else {
                predicates.add(getFieldAdapter(chooseType(value)).toPredicate(entity, criteriaBuilder, key, value, from));
            }
        });
        if (searchFuzzyKeyword != null && !searchFuzzyKeyword.isEmpty()) {
            // 如果关键字不为空，则动态拼接SQL [select ... where ... and ({column_name} like '%value%' or ...)]
            searchFuzzyKeyword.forEach((key, value) -> {
                // 关键字字段必须是对象中存在的
                if (!fields.contains(key)) {
                    return;
                }
                // 且值不为空
                if (!value.toString().isEmpty()) {
                    likePredicates
                            .add(criteriaBuilder.like(from.get(key).as(String.class), "%" + value.toString() + "%"));
                }
            });
            if (!likePredicates.isEmpty()) {
                predicates.add(criteriaBuilder.or(likePredicates.toArray(new Predicate[likePredicates.size()])));
            }
        }
        return predicates;
    }

    /**
     * 获取实体类对象中的字段
     *
     * @param entityClass 实体类对象
     * @param <T>         对象Class 下限为实体类对象
     * @return 字段名称列表
     */
    private static <T extends BasicEntity> List<String> checkFieldName(Class<T> entityClass) {
        List<String> fields = new ArrayList<>();
        // 获取实体类中的私有字段
        for (Field field : entityClass.getDeclaredFields()) {
            fields.add(field.getName());
        }
        // 获取基础实体类中的私有字段
        for (Field field : entityClass.getSuperclass().getDeclaredFields()) {
            fields.add(field.getName());
        }
        String entityName = entityClass.getSimpleName();
        fields.add(((char) (entityName.charAt(0) + 32)) + entityName.substring(1) + "Id");
        return fields;
    }

    /**
     * 检查查询结果，并返回
     *
     * @param entityOptional 查询结果
     * @param <T>            必须为实体类对象
     * @return Result 结果 {@see Result}
     */
    public static <T extends BasicEntity> Result<Object> checkOptional(Optional<T> entityOptional) {
        if (!entityOptional.isPresent()) {
            return ResultUtils.dataEmptyError();
        }
        T baseEntity = entityOptional.get();
        if (!baseEntity.getDataStatus().equals(0)) {
            return ResultUtils.error(ReturnCode.DATA_STATE_ERROR);
        }
        return null;
    }

    /**
     * 根据关键字的值来选择用哪个适配器来处理关键字
     *
     * @param value 关键字检索值
     * @return 关键字检索值类型
     */
    private static EntityFieldTypeEnum chooseType(Object value) {
        if (value instanceof Number) {
            return EntityFieldTypeEnum.NUMBER;
        }
        if (value instanceof String) {
            return EntityFieldTypeEnum.STRING;
        }
        if (value instanceof List) {
            return EntityFieldTypeEnum.LIST;
        }
        if (value instanceof Map) {
            return EntityFieldTypeEnum.MAP;
        }
        return null;
    }

    /**
     * 获取关键字处理器
     *
     * @param typeEnum 关键字类型
     * @return 处理器对象
     */
    public static EntityFieldTypeAdapter<Object> getFieldAdapter(EntityFieldTypeEnum typeEnum) {
        EntityFieldTypeAdapter typeAdapter;
        switch (typeEnum) {
        case NUMBER:
            typeAdapter = FieldTypeAdapterList.NUMBER_FIELD_TYPE_ADAPTER;
            break;
        case LIST:
            typeAdapter = FieldTypeAdapterList.LIST_FIELD_TYPE_ADAPTER;
            break;
        default:
            typeAdapter = FieldTypeAdapterList.STRING_FIELD_TYPE_ADAPTER;
            break;
        }
        return typeAdapter;
    }

    /**
     * 字段值类型枚举
     */
    enum EntityFieldTypeEnum {
        /**
         * 数字类型
         */
        NUMBER,

        /**
         * 字符串类型
         */
        STRING,

        /**
         * 键值对类型
         */
        MAP,

        /**
         * 数组类型
         */
        LIST;

    }

    /**
     * 处理器列表
     */
    static class FieldTypeAdapterList {

        public static final EntityFieldTypeAdapter<Number> NUMBER_FIELD_TYPE_ADAPTER = new NumberFieldTypeAdapter();

        public static final EntityFieldTypeAdapter<String> STRING_FIELD_TYPE_ADAPTER = new StringFieldTypeAdapter();

        public static final EntityFieldTypeAdapter<List<Object>> LIST_FIELD_TYPE_ADAPTER =
                new CollectionFieldTypeAdapter();

    }

}
